<?php
declare(strict_types=1);

namespace app\service;

/**
 * CoordinateService
 * 支持：wgs84 | gcj02 | bd09 | bd09ll | beidou 互转
 * - 使用官方系数 + 迭代法，误差 < 1m（PHP 实现）
 * - 注意：中国境内 WGS84 与 GCJ02 存在火星偏移
 */
class CoordinateService
{
    public function convert(float $lat, float $lon, string $from, string $to): array
    {
        $from = strtolower($from);
        $to = strtolower($to);
        if ($from === $to) {
            return ['lat' => $lat, 'lon' => $lon];
        }

        // 统一到 WGS84
        [$wlat, $wlon] = match ($from) {
            'wgs84' => [$lat, $lon],
            'gcj02' => $this->gcj02_to_wgs84($lat, $lon),
            'bd09', 'bd09ll' => $this->bd09_to_wgs84($lat, $lon),
            'beidou' => [$lat, $lon], // CGCS2000 与 WGS84 近似等效（工程上视为一致）
            default => [$lat, $lon],
        };

        // 从 WGS84 到目标
        [$tlat, $tlon] = match ($to) {
            'wgs84' => [$wlat, $wlon],
            'gcj02' => $this->wgs84_to_gcj02($wlat, $wlon),
            'bd09', 'bd09ll' => $this->wgs84_to_bd09($wlat, $wlon),
            'beidou' => [$wlat, $wlon],
            default => [$wlat, $wlon],
        };

        return ['lat' => round($tlat, 7), 'lon' => round($tlon, 7)];
    }

    // WGS84 <-> GCJ02
    private function wgs84_to_gcj02(float $lat, float $lon): array
    {
        if ($this->out_of_china($lat, $lon)) return [$lat, $lon];
        $d = $this->delta($lat, $lon);
        return [$lat + $d[0], $lon + $d[1]];
    }

    private function gcj02_to_wgs84(float $lat, float $lon): array
    {
        if ($this->out_of_china($lat, $lon)) return [$lat, $lon];
        // 迭代法提升到 <1m
        $initLat = $lat; $initLon = $lon;
        $wgsLat = $lat; $wgsLon = $lon;
        for ($i = 0; $i < 10; $i++) {
            [$glat, $glon] = $this->wgs84_to_gcj02($wgsLat, $wgsLon);
            $dlat = $lat - $glat;
            $dlon = $lon - $glon;
            $wgsLat += $dlat;
            $wgsLon += $dlon;
            if (abs($dlat) < 1e-7 && abs($dlon) < 1e-7) break;
        }
        return [$wgsLat, $wgsLon];
    }

    // WGS84 <-> BD09
    private function wgs84_to_bd09(float $lat, float $lon): array
    {
        [$gLat, $gLon] = $this->wgs84_to_gcj02($lat, $lon);
        return $this->gcj02_to_bd09($gLat, $gLon);
    }

    private function bd09_to_wgs84(float $lat, float $lon): array
    {
        [$gLat, $gLon] = $this->bd09_to_gcj02($lat, $lon);
        return $this->gcj02_to_wgs84($gLat, $gLon);
    }

    private function gcj02_to_bd09(float $lat, float $lon): array
    {
        $x = $lon; $y = $lat;
        $z = sqrt($x * $x + $y * $y) + 0.00002 * sin($y * M_PI);
        $theta = atan2($y, $x) + 0.000003 * cos($x * M_PI);
        $bd_lon = $z * cos($theta) + 0.0065;
        $bd_lat = $z * sin($theta) + 0.006;
        return [$bd_lat, $bd_lon];
    }

    private function bd09_to_gcj02(float $lat, float $lon): array
    {
        $x = $lon - 0.0065; $y = $lat - 0.006;
        $z = sqrt($x * $x + $y * $y) - 0.00002 * sin($y * M_PI);
        $theta = atan2($y, $x) - 0.000003 * cos($x * M_PI);
        $gcj_lon = $z * cos($theta);
        $gcj_lat = $z * sin($theta);
        return [$gcj_lat, $gcj_lon];
    }

    private function delta(float $lat, float $lon): array
    {
        $a = 6378245.0; // Krasovsky 1940
        $ee = 0.00669342162296594323;
        $dLat = $this->transformLat($lon - 105.0, $lat - 35.0);
        $dLon = $this->transformLon($lon - 105.0, $lat - 35.0);
        $radLat = $lat / 180.0 * M_PI;
        $magic = sin($radLat);
        $magic = 1 - $ee * $magic * $magic;
        $sqrtMagic = sqrt($magic);
        $dLat = ($dLat * 180.0) / (($a * (1 - $ee)) / ($magic * $sqrtMagic) * M_PI);
        $dLon = ($dLon * 180.0) / ($a / $sqrtMagic * cos($radLat) * M_PI);
        return [$dLat, $dLon];
    }

    private function out_of_china(float $lat, float $lon): bool
    {
        return $lon < 72.004 || $lon > 137.8347 || $lat < 0.8293 || $lat > 55.8271;
    }

    private function transformLat(float $x, float $y): float
    {
        $ret = -100.0 + 2.0 * $x + 3.0 * $y + 0.2 * $y * $y + 0.1 * $x * $y + 0.2 * sqrt(abs($x));
        $ret += (20.0 * sin(6.0 * $x * M_PI) + 20.0 * sin(2.0 * $x * M_PI)) * 2.0 / 3.0;
        $ret += (20.0 * sin($y * M_PI) + 40.0 * sin($y / 3.0 * M_PI)) * 2.0 / 3.0;
        $ret += (160.0 * sin($y / 12.0 * M_PI) + 320 * sin($y * M_PI / 30.0)) * 2.0 / 3.0;
        return $ret;
    }

    private function transformLon(float $x, float $y): float
    {
        $ret = 300.0 + $x + 2.0 * $y + 0.1 * $x * $x + 0.1 * $x * $y + 0.1 * sqrt(abs($x));
        $ret += (20.0 * sin(6.0 * $x * M_PI) + 20.0 * sin(2.0 * $x * M_PI)) * 2.0 / 3.0;
        $ret += (20.0 * sin($x * M_PI) + 40.0 * sin($x / 3.0 * M_PI)) * 2.0 / 3.0;
        $ret += (150.0 * sin($x / 12.0 * M_PI) + 300.0 * sin($x / 30.0 * M_PI)) * 2.0 / 3.0;
        return $ret;
    }
}